"""Unit and regression tests for Notin."""

import hashlib
import io
import os
import sys
import textwrap
import unittest

try:
    from unittest import mock
except ImportError:
    import mock

import pkg_resources

import notin


class CapturingTestCase(unittest.TestCase):

    """A test case that captures stderr."""

    def setUp(self):
        super(CapturingTestCase, self).setUp()
        self.stdout = sys.stdout = io.StringIO()
        self.stderr = sys.stderr = io.StringIO()

    def tearDown(self):
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__


class LogTests(CapturingTestCase):

    """Unit and regression tests for notin's log() function."""

    def test_simple_message(self):
        notin.log("some message")
        target_output = "{}: some message\n".format(sys.argv[0])
        self.assertEqual(self.stderr.getvalue(), target_output)

    def test_formated_message(self):
        notin.log("format: {} {keyword}", 'positional', keyword='keyword')
        target_output = "{}: format: positional keyword\n".format(sys.argv[0])
        self.assertEqual(self.stderr.getvalue(), target_output)


class DataTestCase(unittest.TestCase):

    """A test case with paths to data directories."""

    def setUp(self):
        super(DataTestCase, self).setUp()
        self.a = pkg_resources.resource_filename(__name__, 'data/a')
        self.b = pkg_resources.resource_filename(__name__, 'data/b')


class ListfilesTests(DataTestCase):

    """Unit and regression tests for notin's listfiles() function."""

    def test_list_a(self):
        self.assertEqual(list(sorted(notin.listfiles(self.a))), [
            os.path.join(self.a, 'c/same.txt'),
            os.path.join(self.a, 'different.txt'),
            os.path.join(self.a, 'same.txt'),
        ])

    def test_list_a_with_symlink_to_b(self):
        link = os.path.join(self.a, 'b')
        try:
            os.symlink(self.b, link)
        except (NotImplementedError, OSError):
            self.skipTest("unable to create symlink")
        try:
            self.assertEqual(list(sorted(notin.listfiles(self.a))), [
                os.path.join(self.a, 'c/same.txt'),
                os.path.join(self.a, 'different.txt'),
                os.path.join(self.a, 'same.txt'),
            ])
        finally:
            os.remove(link)

    def test_list_a_with_log(self):
        log = mock.Mock()
        list(notin.listfiles(self.a, log))
        log.assert_has_calls([
            mock.call(numfiles=2, dirpath=self.a),
            mock.call(numfiles=1, dirpath=os.path.join(self.a, 'c')),
        ])


class Md5sumTests(DataTestCase):

    """Unit and regression tests for notin's md5sum() function."""

    def test_on_same(self):
        md5sum = notin.md5sum(os.path.join(self.a, 'same.txt'))
        self.assertEqual(md5sum, hashlib.md5(b'abc\n').digest())
        other_md5sum = notin.md5sum(os.path.join(self.b, 'same.txt'))
        self.assertEqual(other_md5sum, md5sum)

    def test_on_different(self):
        md5sum = notin.md5sum(os.path.join(self.a, 'different.txt'))
        self.assertEqual(md5sum, hashlib.md5(b'acb\n').digest())
        other_md5sum = notin.md5sum(os.path.join(self.b, 'different.txt'))
        self.assertNotEqual(other_md5sum, md5sum)


class MainTests(DataTestCase, CapturingTestCase):

    """Unit and regression tests for notin's main() function."""

    def test_scan_a(self):
        sys.argv[1:] = [self.b, self.a]
        notin.main()
        sorted_stdout = list(sorted(self.stdout.getvalue().splitlines()))
        self.assertEqual(sorted_stdout, [
            os.path.join(self.a, 'different.txt'),
        ])
        self.assertEqual(self.stderr.getvalue(), "")

    def test_scan_a_with_verbose_flag(self):
        sys.argv[1:] = ['--verbose', self.b, self.a]
        notin.main()
        sorted_stdout = list(sorted(self.stdout.getvalue().splitlines()))
        self.assertEqual(sorted_stdout, [
            os.path.join(self.a, 'different.txt'),
        ])
        self.assertEqual(self.stderr.getvalue(), textwrap.dedent("""\
            {executable}: Indexing files in {b}
            {executable}: Hashing 3 files in {b}
            {executable}: Hashing 1 files in {bd}
            {executable}: Scaning {a} for unique files
            {executable}: Checking 2 files in {a}
            {executable}: Checking 1 files in {ac}
        """).format(
            executable=sys.argv[0],
            a=self.a, ac=os.path.join(self.a, 'c'),
            b=self.b, bd=os.path.join(self.b, 'd'),
        ))

    def test_scan_b(self):
        sys.argv[1:] = [self.a, self.b]
        notin.main()
        sorted_stdout = list(sorted(self.stdout.getvalue().splitlines()))
        self.assertEqual(sorted_stdout, [
            os.path.join(self.b, 'different.txt'),
            os.path.join(self.b, 'unique.txt'),
        ])
        self.assertEqual(self.stderr.getvalue(), "")

    def test_scan_b_with_verbose_flag(self):
        sys.argv[1:] = ['--verbose', self.a, self.b]
        notin.main()
        sorted_stdout = list(sorted(self.stdout.getvalue().splitlines()))
        self.assertEqual(sorted_stdout, [
            os.path.join(self.b, 'different.txt'),
            os.path.join(self.b, 'unique.txt'),
        ])
        self.assertEqual(self.stderr.getvalue(), textwrap.dedent("""\
            {executable}: Indexing files in {a}
            {executable}: Hashing 2 files in {a}
            {executable}: Hashing 1 files in {ac}
            {executable}: Scaning {b} for unique files
            {executable}: Checking 3 files in {b}
            {executable}: Checking 1 files in {bd}
        """).format(
            executable=sys.argv[0],
            a=self.a, ac=os.path.join(self.a, 'c'),
            b=self.b, bd=os.path.join(self.b, 'd'),
        ))
